﻿namespace AY38910
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;

    public class YmFileReader
    {
        #region Public Methods and Operators

        public YmFile Read(string fileName)
        {
            var file = File.OpenRead(fileName);

            if (file.CanSeek && file.Position != 0)
            {
                file.Position = 0;
            }

            var fileId = file.ReadString(4);
            if (!fileId.StartsWith("YM"))
            {
                // Probably a compressed file
                // Close file and try to decompress
                file.Close();
                file.Dispose();

                // Attempt to decompress
                var process = new Process
                              {
                                  StartInfo =
                                  {
                                      UseShellExecute = false,
                                      RedirectStandardOutput = true,
                                      CreateNoWindow = true,
                                      WindowStyle = ProcessWindowStyle.Hidden,
                                      // RedirectStandardInput = true,
                                      FileName = Path.Combine(Environment.CurrentDirectory, "lib\\7z.exe"), //  "C:\\Program Files\\7-Zip\\7z.exe",
                                      Arguments = "e -so \"" + fileName + "\""
                                  }
                              };

                process.Start();
                var memoryStream = new MemoryStream();
                process.StandardOutput.BaseStream.CopyTo(memoryStream);
                process.WaitForExit();
                process.Close();

                return Read(memoryStream);
            }

            return Read(file);
        }

        #endregion

        #region Methods

        private static List<YmFrame> GetInterlacedFrames(Stream file, int frameCount)
        {
            var frames = new List<YmFrame>(frameCount);

            // Use interleaved format
            // http://leonard.oxg.free.fr/ymformat.html#ymchip
            //  That's why there is an interleaved format: file contains all r0 for all frames, then all r1 for all frame on so on. 
            //  Almost all YM file spreaded on the web are interleaved files.
            byte[] register0Bytes = file.ReadBytes(frameCount);
            byte[] register1Bytes = file.ReadBytes(frameCount);
            byte[] register2Bytes = file.ReadBytes(frameCount);
            byte[] register3Bytes = file.ReadBytes(frameCount);
            byte[] register4Bytes = file.ReadBytes(frameCount);
            byte[] register5Bytes = file.ReadBytes(frameCount);
            byte[] register6Bytes = file.ReadBytes(frameCount);
            byte[] register7Bytes = file.ReadBytes(frameCount);
            byte[] register8Bytes = file.ReadBytes(frameCount);
            byte[] register9Bytes = file.ReadBytes(frameCount);
            byte[] register10Bytes = file.ReadBytes(frameCount);
            byte[] register11Bytes = file.ReadBytes(frameCount);
            byte[] register12Bytes = file.ReadBytes(frameCount);
            byte[] register13Bytes = file.ReadBytes(frameCount);
            byte[] register14Bytes = file.ReadBytes(frameCount);
            byte[] register15Bytes = file.ReadBytes(frameCount);

            var end = file.ReadString();
            //if (end != "End!")
            //{
            //    throw new InvalidDataException("Invalid ending.");
            //}

            for (var i = 0; i < frameCount; i++)
            {
                frames.Add(
                    new YmFrame
                    {
                        VoiceAPeriodCourse = register0Bytes[i],
                        VoiceAPeriodFine = register1Bytes[i],
                        VoiceBPeriodCourse = register2Bytes[i],
                        VoiceBPeriodFine = register3Bytes[i],
                        VoiceCPeriodCourse = register4Bytes[i],
                        VoiceCPeriodFine = register5Bytes[i],
                        NoisePeriod = register6Bytes[i],
                        MixerControl = register7Bytes[i],
                        VolumeControlA = register8Bytes[i],
                        VolumeControlB = register9Bytes[i],
                        VolumeControlC = register10Bytes[i],
                        EnvelopeCourse = register11Bytes[i],
                        EnvelopeFine = register12Bytes[i],
                        EnvelopeShape = register13Bytes[i],
                        IOPortA = register14Bytes[i],
                        IOPortB = register15Bytes[i]
                    });
            }

            return frames;
        }

        private static List<YmFrame> GetYm3InterlacedFrames(Stream file, int frameCount)
        {
            var frames = new List<YmFrame>(frameCount);

            // Use interleaved format
            // http://leonard.oxg.free.fr/ymformat.html#ymchip
            //  That's why there is an interleaved format: file contains all r0 for all frames, then all r1 for all frame on so on. 
            //  Almost all YM file spreaded on the web are interleaved files.
            byte[] register0Bytes = file.ReadBytes(frameCount);
            byte[] register1Bytes = file.ReadBytes(frameCount);
            byte[] register2Bytes = file.ReadBytes(frameCount);
            byte[] register3Bytes = file.ReadBytes(frameCount);
            byte[] register4Bytes = file.ReadBytes(frameCount);
            byte[] register5Bytes = file.ReadBytes(frameCount);
            byte[] register6Bytes = file.ReadBytes(frameCount);
            byte[] register7Bytes = file.ReadBytes(frameCount);
            byte[] register8Bytes = file.ReadBytes(frameCount);
            byte[] register9Bytes = file.ReadBytes(frameCount);
            byte[] register10Bytes = file.ReadBytes(frameCount);
            byte[] register11Bytes = file.ReadBytes(frameCount);
            byte[] register12Bytes = file.ReadBytes(frameCount);
            byte[] register13Bytes = file.ReadBytes(frameCount);

            for (var i = 0; i < frameCount; i++)
            {
                frames.Add(
                    new YmFrame
                    {
                        VoiceAPeriodCourse = register0Bytes[i],
                        VoiceAPeriodFine = register1Bytes[i],
                        VoiceBPeriodCourse = register2Bytes[i],
                        VoiceBPeriodFine = register3Bytes[i],
                        VoiceCPeriodCourse = register4Bytes[i],
                        VoiceCPeriodFine = register5Bytes[i],
                        NoisePeriod = register6Bytes[i],
                        MixerControl = register7Bytes[i],
                        VolumeControlA = register8Bytes[i],
                        VolumeControlB = register9Bytes[i],
                        VolumeControlC = register10Bytes[i],
                        EnvelopeCourse = register11Bytes[i],
                        EnvelopeFine = register12Bytes[i],
                        EnvelopeShape = register13Bytes[i],
                    });
            }

            return frames;
        }

        private static List<YmFrame> GetNonInterlacedFrames(Stream file, int frameCount)
        {
            var frames = new List<YmFrame>(frameCount);

            // Don't use interleaved format, just read frame by frame
            for (var i = 0; i < frameCount; i++)
            {
                var frame = file.ReadBytes(16);
                frames.Add(
                    new YmFrame
                    {
                        VoiceAPeriodCourse = frame[0],
                        VoiceAPeriodFine = frame[1],
                        VoiceBPeriodCourse = frame[2],
                        VoiceBPeriodFine = frame[3],
                        VoiceCPeriodCourse = frame[4],
                        VoiceCPeriodFine = frame[5],
                        NoisePeriod = frame[6],
                        MixerControl = frame[7],
                        VolumeControlA = frame[8],
                        VolumeControlB = frame[9],
                        VolumeControlC = frame[10],
                        EnvelopeCourse = frame[11],
                        EnvelopeFine = frame[12],
                        EnvelopeShape = frame[13],
                        IOPortA = frame[14],
                        IOPortB = frame[15]
                    });
            }

            return frames;
        }

        private static YmFile Read(Stream file)
        {
            var ymFile = new YmFile();

            try
            {
                if (file.CanSeek && file.Position != 0)
                {
                    file.Position = 0;
                }

                ymFile.FileId = file.ReadString(4);
                if (!ymFile.FileId.StartsWith("YM"))
                {
                    throw new InvalidDataException("Not a valid file");
                }

                if (ymFile.FileId.StartsWith("YM3"))
                {
                    var frameCount = (int)((file.Length - 4) / 14);
                    ymFile.FrameCount = (uint)frameCount;
                    ymFile.Frames = GetYm3InterlacedFrames(file, frameCount);
                    ymFile.FrameSpeed = 50;
                    ymFile.SongComment = ymFile.SongName = ymFile.AuthorName = string.Empty;
                }
                else if (ymFile.FileId.StartsWith("YM4") || ymFile.FileId.StartsWith("YM5") || ymFile.FileId.StartsWith("YM6"))
                {
                    ReadYm6File(file, ymFile);
                }
            }
            finally
            {
                file.Close();
                file.Dispose();
            }

            return ymFile;
        }

        private static void ReadYm6File(Stream file, YmFile ymFile)
        {
            ymFile.CheckString = file.ReadString(8);
            ymFile.FrameCount = file.ReadUInt32(true);
            ymFile.SongAttributes = file.ReadUInt32(true);
            ymFile.DigiDrumSampleCount = file.ReadUInt16(true);
            ymFile.MasterClock = file.ReadUInt32(true);
            ymFile.FrameSpeed = file.ReadUInt16(true);
            ymFile.LoopFrame = file.ReadUInt32(true);
            ymFile.Size = file.ReadUInt16(true);

            ymFile.DigiDrumSamples = new List<DigiDrumSample>(ymFile.DigiDrumSampleCount);

            for (var i = 0; i < ymFile.DigiDrumSampleCount; i++)
            {
                var sample = new DigiDrumSample();
                sample.SampleSize = file.ReadUInt32(true);
                sample.Data = file.ReadBytes((int)sample.SampleSize);
                ymFile.DigiDrumSamples.Add(sample);
            }

            ymFile.SongName = file.ReadString();
            ymFile.AuthorName = file.ReadString();
            ymFile.SongComment = file.ReadString();

            var frames = new List<YmFrame>((int)ymFile.FrameCount);

            // The first bit of the SongAttribute represents whether it is interleaved or not
            if ((ymFile.SongAttributes & 0x01) == 1)
            {
                // Use Interlaced format
                ymFile.Frames = GetInterlacedFrames(file, (int)ymFile.FrameCount);
            }
            else
            {
                ymFile.Frames = GetNonInterlacedFrames(file, (int)ymFile.FrameCount);
            }
        }

        #endregion
    }
}